package com.framework.security.handler;

import cn.hutool.core.util.StrUtil;
import com.framework.common.ResultCode;
import com.framework.common.ResultJson;
import com.framework.common.constant.Constants;
import com.framework.common.utils.ServletUtils;
import com.framework.common.utils.StringUtils;
import com.framework.security.util.RedisCache;
import com.framework.security.util.TokenUtil;
import com.projectm.login.entity.LoginUser;
import com.projectm.member.domain.Member;
import com.projectm.member.domain.MemberAccount;
import com.projectm.member.service.MemberAccountService;
import com.projectm.member.service.MemberService;
import com.projectm.project.domain.ProjectAuthNode;
import com.projectm.project.service.ProjectAuthNodeService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;

/**
 * @version V1.0
 * @program: teamwork
 * @package: com.framework.security
 * @description: Token解析
 * @author: lzd
 * @create: 2020-06-25 09:52
 **/
@Component
@Slf4j
public class JwtAuthorizationFilter extends OncePerRequestFilter {

    @Value("${custom.ignored-role-path}")
    private String[] strings;

    @Value("${custom.disabled-path}")
    private String[] disabedPaths;

    @Autowired
    private TokenUtil tokenUtil;

    @Autowired
    private RedisCache redisCache;

    @Autowired
    private MemberService memberService;

    @Autowired
    private MemberAccountService memberAccountService;

    @Autowired
    private ProjectAuthNodeService projectAuthNodeService;

    private boolean ignoreAuthForTest = true;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        String authorization = request.getHeader(Constants.TOKEN);
        String orgCode = request.getHeader(Constants.ORGCODE);
        String url = request.getRequestURI();
        log.info("request:{}", url);
        String requestUrl = url.substring(1).toLowerCase();
        List<String> urlList = Arrays.asList(strings);
        List<String> disabledUrlList = Arrays.asList(disabedPaths);
        if(disabledUrlList.contains(url)) {
            ServletUtils.renderString(response, ResultJson.failure(ResultCode.MSG_EXCEPTION, null, "禁止访问该URL："+url+","+requestUrl).toString());
            return;
        }
        if(StringUtils.isEmpty(authorization)) {
            //对特定的url放行
            if (urlList.contains(url)) {
                chain.doFilter(request, response);
                return;
            }

            log.error("doFilterInternal with authorization:{}", request.getRequestURI());
            ServletUtils.renderString(response, ResultJson.failure(ResultCode.VERIFY_TOKEN_FAIL, null, "未携带认证信息："+url+","+requestUrl).toString());
            return;
        }
        // 请求头中有token，则进行解析，并且设置认证信息
        String token = authorization.replace(Constants.TOKEN_PREFIX, "");
        String userCode = tokenUtil.getUserCode(token);
        if (StringUtils.isNotEmpty(userCode)) {
            LoginUser loginUser = redisCache.getCacheObject(Constants.LOGIN_USER_KEY + userCode);

            //强制重新读取权限
            loginUser = null;

            if(loginUser == null) {
                Member member = memberService.lambdaQuery().eq(Member::getCode, userCode).one();
                if (member == null){
                    //对特定的url放行
                    if (urlList.contains(url)) {
                        chain.doFilter(request, response);
                        return;
                    }

                    ServletUtils.renderString(response, ResultJson.failure(ResultCode.MSG_EXCEPTION, null, "用户不存在:"+userCode).toString());
                    return;
                }
                loginUser = new LoginUser();
                loginUser.setUser(member);

                List<MemberAccount> list = memberAccountService.lambdaQuery().eq(MemberAccount::getMember_code, userCode).list();
                loginUser.getUser().setMemberAccountList(list);
                Set<String> authSet = list.stream().map(MemberAccount::getAuthorize).collect(Collectors.toSet());
                List<ProjectAuthNode> projectAuthNodeList = projectAuthNodeService.lambdaQuery().in(ProjectAuthNode::getAuth, authSet).list();
                list.forEach(memberAccount -> {
                    List<String> nodeList = projectAuthNodeList.parallelStream().filter(auth -> Objects.equals(auth.getAuth().toString(), memberAccount.getAuthorize()))
                            .map(ProjectAuthNode::getNode).collect(Collectors.toList());
                    memberAccount.setNodeList(nodeList);
                });
            }
            //鉴权
            AtomicBoolean contains = new AtomicBoolean(false);
            loginUser.getUser().getMemberAccountList().forEach(memberAccount -> {
                if (StrUtil.isNotEmpty(orgCode)) {
                    if (Objects.equals(memberAccount.getOrganization_code(), orgCode)) {
                        contains.set(memberAccount.getNodeList().contains(requestUrl));
                    }
                } else {
                    contains.set(memberAccount.getNodeList().contains(requestUrl));
                }

            });
            if (contains.get()) {
            //if (true) {
                UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(loginUser, null, null);
                SecurityContextHolder.getContext().setAuthentication(authentication);
                chain.doFilter(request, response);
            } else {
                //对特定的url放行
                if (urlList.contains(url)) {
                    chain.doFilter(request, response);
                    return;
                }

                ServletUtils.renderString(response, ResultJson.failure(ResultCode.FORBIDDEN, null, requestUrl).toString());
            }
        } else {
            //对特定的url放行
            if (urlList.contains(url)) {
                chain.doFilter(request, response);
                return;
            }

            ServletUtils.renderString(response, ResultJson.failure(ResultCode.VERIFY_TOKEN_FAIL, null, url+","+requestUrl).toString());
        }
    }
}
